#pragma once
/************************
  实气实朔计算器
  适用范围 -722年2月22日——1959年12月
  平气平朔计算使用古历参数进行计算
  定朔、定气计算使用开普勒椭圆轨道计算，同时考虑了光行差和力学时与UT1的时间差
  古代历算仅在晚期才使用开普勒方法计算，此前多采用一些修正表并插值得到，精度很低，与本程序中
的开普勒方法存在误差，造成朔日计算错误1千多个，这些错误使用一个修正表进行订正。同样，定气部分
也使用了相同的方法时行订正。
  平气朔表的算法(线性拟合)：
  气朔日期计算公式：D = k*n + b  , 式中n=0,1,2,3,...,N-1, N为该式适用的范围
  h表示k不变b允许的误差,如果b不变则k许可误差为h/N
  每行第1个参数为k,第2参数为b
  public中定义的成员可以直接使用
*************************/

// 实朔实气计算器;
#include "Calendar/Festival.hpp"
#include "Date.hpp"
#include "SexagenaryCycle.hpp"
#include <string>

struct SolarTermsCoefficient
{
    double jd;
    double coefficient;
};
enum SolarTerm : unsigned int
{
    LastLightSnow,       // last Xiaoxue
    LastHeavySnow,       // last Daxue
    LastWinterSolstice,  // last Dongzhi
    LastModerateCold,    // last Xiaohan
    LastSevereCold,      // last Dahan
    SpringCommences,     // Lichun
    SpringShowers,       // Yushui
    InsectsWaken,        // Jingzhe
    VernalEquinox,       // Chunfen
    BrightAndClear,      // Qingming
    CornRain,            // Guyu
    SummerCommences,     // Lixia
    CornForms,           // Xiaoman
    CornOnEar,           // Mangzhong
    SummerSolstice,      // Xiaozhi
    ModerateHeat,        // Xiaoshu
    GreatHeat,           // Dashu
    AutumnCommences,     // Liqiu
    EndOfHeat,           // Chushu
    WhiteDew,            // Bailu
    AutumnalEquinox,     // Qiufen
    ColdDew,             // Hanlu
    Frost,               // Shuangjiang
    WinterCommences,     // Lidong
    LightSnow,           // Xiaoxue
    HeavySnow,           // Daxue
    WinterSolstice,      // Dongzhi
    ModerateCold,        // Xiaohan
    SevereCold,          // Dahan
    NextSpringCommences, // next Lichun
};
class SolarTerms
{
public:
    /**
     * @brief [so_low] 低精度定朔计算,在2000年至600，误差在2小时以内(仍比古代日历精准很多);
     *
     * @param W
     * @return double
     */
    double CalcMonthPolicyLow(double W);
    /**
     * @brief [so_high] 较高精度朔;
     *
     * @param W
     * @return double
     */
    double CalcMonthPolicyHigh(double W);
    /**
     * @brief [qi_low]最大误差小于30分钟，平均5分;
     *
     * @param W
     * @return double
     */
    double CalcSolarTermPolicyLow(double W);
    /**
     * @brief [qi_higt]较高精度气;
     *
     * @param W
     * @return double
     */
    double CalcSolarTermPolicyHigh(double W);

    // jd应靠近所要取得的气朔日,qs='气'时，算节气的儒略日;
    // double Calc(double jd, bool qs) noexcept;

    /**
     * @brief 计算气;
     *
     * @param jd
     *         2000起的儒略日;
     * @return double
     */
    double CalcQi(double jd) noexcept;
    /**
     * @brief 计算朔;
     *
     * @param jd
     *         2000起的儒略日;
     * @return double
     */
    double CalcSuo(double jd) noexcept;

    double GetSolarTerm(SolarTerm solarTerm) { return this->MiddleSolarTermArray[solarTerm]; }
    /**
     * @brief
     *
     * @param D
     *
     * @param indexSize
     *         节气表数据索引;
     * @param dataPtr
     *         节气表数据指针;
     * @return double
     */
    double CalcTableValue(double D, int indexSize, unsigned long *dataPtr) noexcept;
    /**
     * @brief 计算平{气/朔)  主要是查表并返回数据;
     *
     * @param jd
     *         儒略日;
     * @param data
     *         表指针;
     * @param size
     *         表数据大小;
     * @param pc
     *         常量;
     * @return double 平{气/朔) 的儒略日;
     */
    double LookUpTheTable(const double jd, SolarTermsCoefficient *data, const size_t size, const size_t pc) noexcept;

public:
    /**
     * @brief 农历排月序计算,可定出农历,有效范围：两个冬至之间(冬至一 <= d < 冬至二);
     *
     * @param jd
     *         儒略年;
     * @return double
     */
    void CalcYear(double jd) noexcept;

    /**
     * @brief 获取农历月的中文名称;
     *
     * @param y
     *         农历年;
     * @param lmIndex
     *         农历月份索引,并非农历月,应为会出现润月的情况;
     * @return std::string
     */
    std::string GetLunarMonthName(int y, int lmIndex);

    /**
     * @brief 获取本日节气信息;
     *
     * @param jd
     *        J2000 起的儒略日;
     * @param date
     *        农历日;
     * @param festival
     *        节日信息;
     */
    void GetDayName(double jd, Date &date, Festival &festival) noexcept;

    /**
     * @brief 填充干支纪元信息;
     * 
     * @param jd 
     *         j2000 起儒略日;
     * @param sc 
     *         干支纪元的结构;
     */
    void GetSexagenaryCycleDate(double jd, SexagenaryCycleDate &sc) noexcept;

public:
    /**
     * @brief [qi_accurate]精气;
     *
     * @param W 太阳真黄经值
     * @return double
     */
    static double CalcQiAccurate(double W) noexcept;
    /**
     * @brief [so_accurate]精朔;
     *
     * @param W 视黄经
     * @return double
     */
    static double CalcSuoAccurate(double W) noexcept;
    /**
     * @brief [qi_accurate]精气;
     *
     * @param jd
     *          儒略日期;
     * @return double
     */
    static double CalcQiAccurateOnJulianDate(double jd) noexcept;
    /**
     * @brief [so_accurate]精朔;
     *
     * @param jd
     *          儒略日期;
     * @return double
     */
    static double CalcSuoAccurateOnJulianDate(double jd) noexcept;

// 全年农历最大月份数量;
#define LunarMonthMax (14)
#define SolarTermsMax (SolarTerm::NextSpringCommences + 1)
private:
    // ZQ 中气表,全年节气和包含去年最后俩个节气[0,1];
    double MiddleSolarTermArray[SolarTermsMax];

    // HS 日月合朔表(整日),全年农历月;
    double CloseSuoArray[LunarMonthMax + 1];
    // 闰月位置;
    int LeapIndex;
    // dx 月份天数;
    int DayCountOfMonthArray[LunarMonthMax];
    // ym 月序;各月数字号码;
    int YearCountArray[LunarMonthMax];
};
